'use strict';

/*-------------------------------------------------------------
       _                         
 _   _(_) __ _ _ __ _____      __
| | | | |/ _` | '__/ _ \ \ /\ / /
| |_| | | (_| | | | (_) \ V  V / 
\__ , |_|\__, |_|  \___/ \_/\_/  
 |___/   |___/ (C)2023 shhoo.cn

 1、查询可入网设备
 2、设置入网设备名称及路数
-------------------------------------------------------------*/
/*-----------时间--------*/
import moment from 'moment';
moment.locale('zh-cn');
import { RedisGatewayClass } from "../lib/redis_gateway";
import * as ModbusRTURequest from "../public/modbus/rtu-request"
import { ModbusRTUResponse } from "../public/modbus/rtu-response"
const events = require("events");
const EventEmitter = events.EventEmitter || events;
const JS_NAME = 'loRaComCtrl.ts';
const LORA_BROADCAST_ADDRESS = "00000071";//lora广播地址
const loRaGatewaySendHead = Buffer.from([0x61]); //loRa网关发送数据头
const loRaGatewaySendOthers = Buffer.from([0x00, 0x01, 0x00]); //loRa网关发送数据其他头
/**
 * 
 * @param channel 
 * @param message 
 * @example
 * let test = { "isRight": true, "msg": "ModBus Cmd", "addr": "36240200", "lsnr": 4.2, "rssi": -70, "datr": "SF9BW125", "recvData": "01030A000100000000000100001AFA" }
 * _onReceive("SERIAL_CMD_ACK", JSON.stringify(test))
 */
function _onReceive(channel: string, message: string) {
      const modbus = this;
      // console.log(channel, message)
      let ack = JSON.parse(message);
      if (channel == 'SERIAL_CMD_ACK' && ack.msg == 'ModBus Cmd') {
            // console.log(`${new Date().toLocaleString()}`, channel, ack.msg, ack.recvData)
            try {
                  let mbus = ModbusRTUResponse.fromBuffer(Buffer.from(ack.recvData, 'hex'));
                  // console.log(mbus)
                  if (mbus != null && mbus._address == 1) {//站号为1
                        const addr = ack.addr;
                        switch (mbus._body._fc) {
                              case 1://readCoils(DO)

                                    break;
                              case 2://readDiscreteInputs(DI)

                                    break;
                              case 3://readHoldingRegisters(QueryApplayDevice)
                                    let frameId = mbus._body._values[0];
                                    if (frameId == 0) {//申号入网01030F 00 000C 0008 0008 0032 000B 0AE0 48F0 3111
                                          //                                         0             2             4            6            8            10             12        14 
                                          // 站号(1byte) 功能码(1byte) 数据长度(1byte)  帧标识    DO数量(2byte) DI数量(2byte) AI数量(2byte) hwVer(2byte) swVer(2byte) SNR(2byte) RSSI(2byte) CRC16(2byte)
                                          // 01         03           0F              0000           000C          0008         0008          0032         000A         0AE0       22F0        2271
                                          // 01         03           10              0000           001000080000000B000C01E0BAF02946
                                          console.log(`设备入网`)
                                          let jugeSnr = mbus._body._valuesAsBuffer.slice(13, 14).toString('hex').toUpperCase();
                                          let lsnr = mbus._body._valuesAsBuffer.slice(12, 13)[0];
                                          lsnr = jugeSnr == `F0` ? -lsnr : lsnr;
                                          let jugeRssi = mbus._body._valuesAsBuffer.slice(15, 16).toString('hex').toUpperCase();
                                          let rssi = mbus._body._valuesAsBuffer.slice(14, 15)[0];
                                          rssi = jugeRssi == `F0` ? -rssi : rssi;
                                          let swVer = `V` + (`${mbus._body._valuesAsBuffer.slice(8, 9)[0]}` + mbus._body._valuesAsBuffer.slice(9, 10).toString('hex').toUpperCase()).split("").join(".");
                                          let hwVer = `V` + (`${mbus._body._valuesAsBuffer.slice(10, 11)[0]}` + mbus._body._valuesAsBuffer.slice(11, 12).toString('hex').toUpperCase()).split("").join(".");
                                          const transaction_FC3 = modbus._transactions[`QueryApplayDevice`];
                                          console.log(`${new Date().toLocaleString()}`, `\x1b[30;33m【设备入网】地址为：${addr},DONum:${mbus._body._values[1]},DINum:${mbus._body._values[2]},AINum:${mbus._body._values[3]},lsnr:${lsnr},rssi:${rssi},glsnr:${ack.lsnr},grssi:${ack.rssi},swVer:${swVer},hwVer:${hwVer}\x1b[30;337m`);
                                          if (transaction_FC3.next) {
                                                transaction_FC3.isCtrlOk = true;
                                                transaction_FC3.next(null, {
                                                      addr: addr,
                                                      DONum: mbus._body._values[1],
                                                      DINum: mbus._body._values[2],
                                                      AINum: mbus._body._values[3],
                                                      glsnr: ack.lsnr,
                                                      grssi: ack.rssi,
                                                      lsnr: lsnr,
                                                      rssi: rssi,
                                                      swVer: swVer,
                                                      hwVer: hwVer
                                                })
                                                transaction_FC3.next = null;
                                          }
                                    } else if (frameId == 1) {//writeRegisters(MULT_TIMER_DOS)
                                          // 【loRaComCtrl.ts/<WriteTimerCoils>(addr:E240100B)】--->>>61E240100B000100   01 10 0000 0004 08 0000 00000001006E6656
                                          //01 03 02 00010000000000020000000A00000006000000080000000400000001000800030008000B000800070008000900080005000829FA
                                          // console.log(mbus)
                                          console.log(`\x1b[30;31m【${addr}】收到定时DO控制回执，起始地址：${mbus._body._values[1]},控制数量：${(mbus._body._values.length - 1) / 2}，[通道，时长]=[${mbus._body._values.slice(1)}]\x1b[30;37m`)
                                          const transaction_FC16_0 = modbus._transactions[`WriteTimerCoils_${addr}_${mbus._body._values[1]}_${(mbus._body._values.length - 1) / 2}`];
                                          // console.log(transaction_FC16_0)
                                          if (transaction_FC16_0.next) {
                                                transaction_FC16_0.isCtrlOk = true;
                                                let openSec: number[] = [];
                                                for (let i = 1; i < mbus._body._values.length; i++) {
                                                      if (i % 2 == 0) {
                                                            const element = mbus._body._values[i];
                                                            openSec.push(element);
                                                      }
                                                }
                                                console.log(openSec)
                                                transaction_FC16_0.next(null, {
                                                      start: mbus._body._values[1],
                                                      quantity: mbus._body._values.length - 1,
                                                      adressArr: transaction_FC16_0.adressArr,
                                                      openTimeSecArr: openSec,
                                                })
                                                transaction_FC16_0.next = null;
                                          }

                                    }


                                    break;
                              case 4://readInputRegisters(AI)

                                    break;
                              case 5://writeCoil(DO)
                                    const transaction_FC5 = modbus._transactions[`WriteCoil_${addr}_${mbus._body._address}_${mbus._body._value}`];
                                    // console.log(mbus,transaction_FC5)
                                    if (transaction_FC5.next) {
                                          console.log(transaction_FC5.address, mbus._body._address && transaction_FC5.value, mbus._body._value)
                                          if (transaction_FC5.address == mbus._body._address && transaction_FC5.value == mbus._body._value) {
                                                transaction_FC5.isCtrlOk = true;
                                                transaction_FC5.next(null, {
                                                      address: transaction_FC5.address,
                                                      value: transaction_FC5.value
                                                })
                                                transaction_FC5.next = null;
                                          }
                                    }

                                    break;
                              case 6://writeRegister(TIMER_DO)
                                    const transaction_FC6 = modbus._transactions[`WriteTimerCoil_${addr}_${mbus._body._address}`];
                                    // console.log(transaction_FC6)
                                    if (transaction_FC6.next) {
                                          // console.log(transaction_FC6.address, mbus._body._address && transaction_FC6.value, mbus._body._value)
                                          if (transaction_FC6.address == mbus._body._address && transaction_FC6.value == mbus._body._value) {
                                                transaction_FC6.isCtrlOk = true;
                                                transaction_FC6.next(null, {
                                                      address: transaction_FC6.address,
                                                      value: transaction_FC6.value
                                                })
                                                transaction_FC6.next = null;
                                          }
                                    }
                                    break;
                              case 15://writeCoils(DO)
                                    const transaction_FC15 = modbus._transactions[`WriteCoils_${addr}`];
                                    if (transaction_FC15.next) {
                                          // console.log(transaction_FC15.start, mbus._body._start && transaction_FC15.quantity, mbus._body._quantity)
                                          if (transaction_FC15.start == mbus._body._start && transaction_FC15.quantity == mbus._body._quantity) {
                                                transaction_FC15.isCtrlOk = true;
                                                transaction_FC15.next(null, {
                                                      start: mbus._body._start,
                                                      quantity: mbus._body._quantity,
                                                      values: transaction_FC15.values,
                                                })
                                                transaction_FC15.next = null;
                                          }
                                    }
                                    break;
                              case 16:
                                    // console.log(mbus)
                                    const transaction_FC16_1 = modbus._transactions[`WriteDelayCoils_${addr}`];
                                    if (transaction_FC16_1.next) {
                                          console.log(transaction_FC16_1.start, mbus._body._start, transaction_FC16_1.quantity, mbus._body._quantity)
                                          if (transaction_FC16_1.start == mbus._body._start || 0xffff == mbus._body._start) {
                                                transaction_FC16_1.isCtrlOk = true;
                                                transaction_FC16_1.next(null, {
                                                      coils16Sta: mbus._body._start,
                                                      delay: mbus._body._quantity,
                                                      coilsStatusArr: transaction_FC16_1.coilsStatusArr
                                                })
                                                transaction_FC16_1.next = null;
                                          }
                                    }
                                    break;

                              default:
                                    break;
                        }

                  }
            } catch (error) {

            }

      }

}


/**
 * 
 * @param funcName 函数名
 * @param cmdPacket 
 * @param transactionId 
 * @param retryInterval 
 * @param retryNumber 
 * @param isBackOff 
 */
function _writeCmdToRedisGateway(funcName: string, cmdPacket: string, transactionId: string, retryInterval: number, retryNumber: number, isBackOff: boolean) {
      const transaction = this._transactions[transactionId];
      console.log(transaction)
      if (transaction) {
            if (transaction.interval != null) {
                  clearInterval(transaction.interval);
                  transaction.interval = null;
            }
            // this.RedisGateway.PubSerialDirectCmd(cmdPacket, 1);
            // this.RedisGateway.PubSerialDirectCmd("61000000710001000D0A0D0A0D0A", 1);
            this.RedisGateway.PubSerialDirectCmd(cmdPacket, 1);
            console.log(`${new Date().toLocaleString()}`, `\x1b[30;32m【${`${JS_NAME}/<${funcName}>`}(addr:${transaction.addr})】--->>>${cmdPacket}\x1b[30;37m`);
            transaction.interval = setInterval(() => {
                  let perTimes = Math.round(retryInterval / 200);
                  let reCnt = isBackOff ? Math.round(perTimes * (1 + transaction.retryCount / 2)) : perTimes;
                  if (transaction.cnt >= reCnt) {
                        if (transaction.isCtrlOk == false && transaction.addr != LORA_BROADCAST_ADDRESS) {
                              // console.log(reCnt)
                              if (transaction.retryCount < retryNumber) {
                                    console.log(`\x1b[30;33m${`${JS_NAME}/<${funcName}>`}】设备(addr:${transaction.addr})控制尝试重发第${transaction.retryCount + 1}次指令(${cmdPacket})\x1b[30;37m`)
                                    this.RedisGateway.PubSerialDirectCmd(cmdPacket, 1);
                                    transaction.retryCount++;
                              } else {
                                    clearInterval(transaction.interval);
                                    transaction.interval = null;
                                    console.log(`\x1b[30;31m【${`${JS_NAME}/<${funcName}>`}】设备(addr:${transaction.addr})控制尝试重发${retryNumber}次指令(${cmdPacket})控制失败\x1b[30;37m`)
                                    transaction.next(`设备(addr:${transaction.addr})控制尝试重发${retryNumber}次指令控制失败`, null);
                              }
                              transaction.cnt = 0;
                        } else {
                              clearInterval(transaction.interval);
                        }
                  }
                  transaction.cnt++;
            }, 200)
      } else {
            console.log(transaction)
      }
}
class LoRaComCtrlClass extends EventEmitter {
      RedisGateway: RedisGatewayClass
      /**
       * Class making LoRaComCtrl calls fun and easy.
       *
       */
      constructor() {
            super();
            this._transactions = {};
            this._onReceive = _onReceive.bind(this);
      }

      /**
       * Open the redisGateway and register LoRaComCtrl parsers
       *
       *      of failure.
       */
      open() {
            const modbus = this;
            this.RedisGateway = new RedisGatewayClass();
            this.RedisGateway.on("message", modbus._onReceive);
      }


      /**
       * 设备入网查询
       * @param addr LoRa地址
       * @param stationNumber 站号
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 回调
       * @example
       * LoRaComCtrl.QueryApplayDevice("00000071",1, 1500, 2, false, function (err: string | null, data: object | null) {
           console.log(`==============TEST QueryApplayDevice==============`)
           console.log(err, data)
         })
       */
      QueryApplayDevice(addr: string, stationNumber: number, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            let deviceHead = loRaGatewaySendHead;
            let deviceOthers = loRaGatewaySendOthers;
            let ackFrameBuf = ModbusRTURequest.ReadHoldingRegisters(stationNumber, 0, 8);
            let cmdPacket = Buffer.concat([deviceHead, Buffer.from(addr, 'hex'), deviceOthers, ackFrameBuf]);
            // set state variables
            let transactions_id = `QueryApplayDevice`;
            this._transactions[transactions_id] = {
                  addr: addr,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            _writeCmdToRedisGateway.call(this, `QueryApplayDevice`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }


      /**
       * 写单线圈（控制开关&风机等）
       * @param addr LoRa地址
       * @param stationNumber 站号
       * @param address 写线圈地址
       * @param value 写线圈状态值
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 回调
       * @example
      LoRaComCtrl.WriteCoil(addr,1, 0, true, 2000, 3, false, function (err: string | null, data: object) {
            console.log(err, data)
      })
       */
      WriteCoil(addr: string, stationNumber: number, address: number, value: boolean, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            let deviceHead = loRaGatewaySendHead;
            let deviceOthers = loRaGatewaySendOthers;
            let ackFrameBuf = ModbusRTURequest.WriteSingleCoil(stationNumber, address, value);
            let cmdPacket = Buffer.concat([deviceHead, Buffer.from(addr, 'hex'), deviceOthers, ackFrameBuf]);
            let transactions_id = `WriteCoil_${addr}_${address}_${value}`;
            this._transactions[transactions_id] = {
                  addr: addr,
                  address: address,
                  value: value,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            _writeCmdToRedisGateway.call(this, `WriteCoil`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }
      /**
       * 写多个连续DO状态
       * @param addr 地址
       * @param stationNumber 站号 
       * @param start 起始DO地址
       * @param values 设定的多个连续DO的状态值
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 
       * @example
      LoRaComCtrl.WriteCoils(addr, 1, 0, [false, false, false, false, false, false, false, false, false, false, false, false], 2000, 3, true, function (err: string | null, data: WriteCoilInfo) {
           console.log(err, data)
      })
       */
      WriteCoils(addr: string, stationNumber: number, start: number, values: Array<boolean>, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            let deviceHead = loRaGatewaySendHead;
            let deviceOthers = loRaGatewaySendOthers;
            let ackFrameBuf = ModbusRTURequest.WriteMultipleCoils(stationNumber, start, values, values.length);
            let cmdPacket = Buffer.concat([deviceHead, Buffer.from(addr, 'hex'), deviceOthers, ackFrameBuf]);
            let transactions_id = `WriteCoils_${addr}`;
            this._transactions[transactions_id] = {
                  addr: addr,
                  start: start,
                  values: values,
                  quantity: values.length,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            _writeCmdToRedisGateway.call(this, `WriteCoils`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }
      /**
       * 写定时单线圈（控制值电磁阀设备）
       * @param addr 地址
       * @param stationNumber 站号 
       * @param address DO地址
       * @param openTimeSec 开启时长
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 
       * @example
      LoRaComCtrl.WriteTimerCoil(addr, 1, 0, 30, 2000, 3, true, function (err: string | null, data: WriteTimerCoilInfo) {
            console.log(err, data)
      })
       */
      WriteTimerCoil(addr: string, stationNumber: number, address: number, openTimeSec: number, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            //6 1E240100 B000100 01 100000000102001E265
            let deviceHead = loRaGatewaySendHead;
            let deviceOthers = loRaGatewaySendOthers;
            let ackFrameBuf = ModbusRTURequest.WriteSingleRegister(stationNumber, address, openTimeSec);
            let cmdPacket = Buffer.concat([deviceHead, Buffer.from(addr, 'hex'), deviceOthers, ackFrameBuf]);
            let transactions_id = `WriteTimerCoil_${addr}_${address}`;
            console.log(transactions_id)
            this._transactions[transactions_id] = {
                  addr: addr,
                  address: address,
                  value: openTimeSec,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            _writeCmdToRedisGateway.call(this, `WriteTimerCoil`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }
      /**
       * 写定时线圈（控制卷膜拉幕设备）
       * @param addr 地址
       * @param stationNumber 站号 
       * @param adressArr 控制的DO地址
       * @param openTimeSecArr 控制的DO开启时长
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 
       * @example
      LoRaComCtrl.WriteTimerCoils(addr, 1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 10], [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110], 6000, 3, true, function (err: string | null, data: WriteTimerCoilsInfo) {
            console.log(err, data)
      })
       */
      WriteTimerCoils(addr: string, stationNumber: number, adressArr: Array<number>, openTimeSecArr: Array<number>, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            console.log(addr, stationNumber, adressArr, openTimeSecArr, retryInterval, retryNumber, isBackOff)
            let deviceHead = loRaGatewaySendHead;
            let deviceOthers = loRaGatewaySendOthers;
            const payload = Buffer.alloc(adressArr.length * 4)
            for (let i = 0; i < adressArr.length; i++) {
                  payload.writeUInt16BE(adressArr[i], i * 4); // 
                  payload.writeUInt16BE(openTimeSecArr[i], (i * 4) + 2); // start address
            }
            // console.log(payload)
            //61E240100B000100 01 10 0000 0004 08 0008 0033 0009 0000 EBBC
            let ackFrameBuf = ModbusRTURequest.WriteMultipleRegisters(stationNumber, 0, payload);//寄存器0为定时DO控制寄存器，回执起始地址为控制起始地址，数量为控制DO数量
            let cmdPacket = Buffer.concat([deviceHead, Buffer.from(addr, 'hex'), deviceOthers, ackFrameBuf]);
            let transactions_id = `WriteTimerCoils_${addr}_${adressArr[0]}_${adressArr.length}`;
            console.log(transactions_id)
            this._transactions[transactions_id] = {
                  addr: addr,
                  start: 0,
                  timerCoils: 2,
                  quantity: adressArr.length * 2,
                  adressArr: adressArr,
                  openTimeSecArr: openTimeSecArr,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };
            // console.log(cmdPacket)

            _writeCmdToRedisGateway.call(this, `WriteTimerCoils`, cmdPacket.toString('hex').toUpperCase(), transactions_id, Math.floor(retryInterval + Math.random() * 1000), retryNumber, isBackOff)
      }
      /**
       * 延时启动置线圈
       * @param addr 地址
       * @param stationNumber 站号 
       * @param coilsStatusArr 线圈状态
       * @param delay 延时时间（ms）
       * @param retryInterval 重发间隔(ms)
       * @param retryNumber 重发次数（ms）
       * @param isBackOff 是否退避
       * @param next 
       * @example
      LoRaComCtrl.WriteDelayCoils(addr, 1, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 10, 6000, 3, true, function (err: string | null, data: object) {
            console.log(err, data)
      })
       */
      WriteDelayCoils(addr: string, stationNumber: number, coilsStatusArr: Array<number>, delay: number, retryInterval: number, retryNumber: number, isBackOff: boolean, next: Function) {
            //01 10 00FF 0002 04 FC00 07D0 8F57
            let deviceHead = loRaGatewaySendHead;
            let deviceOthers = loRaGatewaySendOthers;
            const len = coilsStatusArr.length > 16 ? 16 : coilsStatusArr.length;
            let coilsStr = "";
            for (let i = 0; i < len; i++) {
                  coilsStr = `${coilsStr}${coilsStatusArr[i]}`
            }
            let coilsValue = parseInt(coilsStr.padEnd(16, "0"), 2);
            console.log(coilsStr.padEnd(16, "0"), coilsValue)
            const payload = Buffer.alloc(4)
            payload.writeUInt16BE(coilsValue, 0); // 
            payload.writeUInt16BE(delay, 2); // 
            //61 E240100B 000100 01 10 0001 0004 08 0FFF 000A 000000009CF7
            // console.log(coilsValue)
            let ackFrameBuf = ModbusRTURequest.WriteMultipleRegisters(stationNumber, 255, payload);//协议定义寄存器 255为延迟启动寄存器地址
            // console.log(ackFrameBuf)
            let cmdPacket = Buffer.concat([deviceHead, Buffer.from(addr, 'hex'), deviceOthers, ackFrameBuf]);
            let transactions_id = `WriteDelayCoils_${addr}`;
            this._transactions[transactions_id] = {
                  addr: addr,
                  start: coilsValue,
                  coilsStatusArr: coilsStatusArr,
                  quantity: delay,
                  isCtrlOk: false,
                  retryCount: 0,
                  cnt: 0,
                  interval: null,
                  next: next
            };

            _writeCmdToRedisGateway.call(this, `WriteDelayCoils`, cmdPacket.toString('hex').toUpperCase(), transactions_id, retryInterval, retryNumber, isBackOff)
      }

}

export {
      LoRaComCtrlClass
}